/*
    Copyright (C) 2013  Letian Yi  <letian0805@gmail.com>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
#include "chat.h"

static const char *g_cmd[CMD_LAST] = {
    [CMD_LOGIN] = "LOGIN",
    [CMD_WHOISON] = "WHOISON",
    [CMD_LOGOUT] = "LOGOUT",
    [CMD_GETIELIST] = "ONLINELIST",
};

int chat_add_events(ChatClient *cli, int events)
{
    struct epoll_event epv;
    cli->events = events;
    epv.events = events;
    epv.data.ptr = cli;

    int op;
    if (cli->status!=0){
        op = EPOLL_CTL_MOD;
    }else{
        op = EPOLL_CTL_ADD;
    }
    if (epoll_ctl(cli->epfd, op, cli->sktfd, &epv)<0){
        printf(" failed to set event: %s\n", strerror(errno));
        return -1;
    }
    cli->status = 1;
    return 0;
}

int chat_remove_events(ChatClient *cli)
{
    if (cli->status==0)
        return 0;

    struct epoll_event epv;
    epv.events = 0;
    epv.data.ptr = cli;
    
    if (epoll_ctl(cli->epfd, EPOLL_CTL_DEL, cli->sktfd, &epv)<0){
        printf(" failed to remove event\n");
        return -1;
    }

    cli->events = 0;
    cli->status = 0;

    return 0;
}

int fd_add_events(int epfd, int fd, int events)
{
    struct epoll_event epv;
    epv.events = events;
    epv.data.fd = fd;
    if (epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &epv)<0){
        printf(" failed to add event\n");
        return -1;
    }
    return 0;
}

int fd_remove_events(int epfd, int fd)
{
    struct epoll_event epv;
    epv.events = 0;
    epv.data.fd = fd;
    if (epoll_ctl(epfd, EPOLL_CTL_DEL, fd, &epv)<0){
        printf(" failed to remove event\n");
        return -1;
    }
    return 0;
}


int socket_readline(int sktfd, char *buf)
{
    int count = 0;
    while(1){
        if (read(sktfd, buf+count, 1)<1){
            break;
        }
        if (buf[count]=='\n'){
            buf[count] = '\0';
            return count;
        }
        count++;
    }

    return -1;
}

#if 1
int socket_writeline(int fd,char *buffer,int length)
{
    int bytes_left;
    int written_bytes;
    char *ptr;
    ptr=buffer;
    bytes_left=length;
    while(bytes_left>0){
        /* 开始写*/
        written_bytes=write(fd,ptr,bytes_left);
        if(written_bytes<=0) /* 出错了*/          {
            if(errno==EINTR) /* 中断错误 我们继续写*/
                written_bytes=0;
            else
                /* 其他错误 没有办法,只好撤退了*/
                return(-1);
        }
        bytes_left-=written_bytes;
        ptr+=written_bytes;
        /* 从剩下的地方继续写   */
    }
    return 0;
}
#else
int socket_writeline(int sktfd, char *line, int len)
{
    if (write(sktfd, line, len)<len)
        return -1;

    return 0;
}
#endif

int packet_free(ChatPacket *pkt)
{
    if (pkt==NULL){
        return -1;
    }

    int i;
    for (i=0; i<pkt->nmsg; i++){
        SAFE_FREE(pkt->msg[i]);
    }
    SAFE_FREE(pkt->from);
    SAFE_FREE(pkt->to);
    SAFE_FREE(pkt->time);
    SAFE_FREE(pkt->fdbk);
    SAFE_FREE(pkt->time);
    SAFE_FREE(pkt);
    return 0;
}

char *gettime(void)
{
    char buf[MAXLEN] = {0};
    time_t timenow = time(0);
    struct tm *tmn = localtime(&timenow);
    sprintf(buf,"%4d-%02d-%02d %02d:%02d:%02d ",tmn->tm_year+1900,
            tmn->tm_mon+1,tmn->tm_mday,tmn->tm_hour,tmn->tm_min,tmn->tm_sec);
    return strdup(buf);
}

int client_flush(ChatClient *cli)
{
    int ret = 0;
    ChatPacket *pkt = cli->pktsnd;
    if (pkt==NULL){
        return 0;
    }
    if ((ret=socket_send_head(cli->sktfd, HEAD_START, NULL))<0){
        goto FAILED;
    }
    if ((ret=socket_send_head(cli->sktfd, HEAD_FROM, pkt->from))<0){
        goto FAILED;
    }
    if ((ret=socket_send_head(cli->sktfd, HEAD_TO, pkt->to))<0){
        goto FAILED;
    }
    if ((ret=socket_send_head(cli->sktfd, HEAD_TIME, pkt->time))<0){
        goto FAILED;
    }
    if ((ret=socket_send_head(cli->sktfd, HEAD_FDBK, pkt->fdbk))<0){
        goto FAILED;
    }
    int i;
    for (i=0; i<cli->pktsnd->nmsg; i++){
        if((ret=socket_send_head(cli->sktfd, HEAD_MSG, pkt->msg[i]))<0){
            goto FAILED;
        }
    }
    if ((ret=socket_send_head(cli->sktfd, HEAD_END, NULL))<0){
        goto FAILED;
    }
FAILED:
    if (ret<0){
        printf(" failed to flush client\n");
    }
    packet_free(pkt);
    cli->pktsnd = NULL;
    return ret;
}

int client_login(ChatClient *cli)
{
    ChatPacket *pkt = packet_new(cli->name, SERV_NAME);
    pkt->nmsg = 1;
    pkt->msg[0] = strdup(g_cmd[CMD_LOGIN]);
    pkt->time = gettime();
    cli->pktsnd = pkt;
    return client_flush(cli);
}

int send_to_client(ChatClient *cli, const char *msg)
{
    ChatPacket *pkt = packet_new(SERV_NAME, cli->name);
    pkt->nmsg = 1;
    pkt->msg[0] = strdup(msg);
    pkt->time = gettime();
    cli->pktsnd = pkt;
    return client_flush(cli);
}

int client_parse_input(ChatClient *cli, char *input,char **name)
{
    int ret = 0;
    input = str_strip(input);
    if (input==NULL){
        return -1;
    } 
    if (*input == 0) {
        return -1;
    }
    //    printf("[%s] %d\n",input,strlen(input));
    char *to = NULL;
    char *msg = NULL;
    ServerCmd cmd = CMD_LAST;
    packet_free(cli->pktsnd);
    cli->pktsnd = NULL;

    if(strcmp(input, "getIElist")==0){
        *name = SERV_NAME;
        cli->pktsnd = packet_new(cli->name, SERV_NAME);
        cmd = CMD_GETIELIST;
        msg = (char *)g_cmd[cmd];
    }else
    if(strcmp(input, "whoison")==0){
        *name = SERV_NAME;
        cli->pktsnd = packet_new(cli->name, SERV_NAME);
        cmd = CMD_WHOISON;
        msg = (char *)g_cmd[cmd];
    }else if(strcmp(input, "help")==0){
        printf("\rto usr: msg     --------- send msg to 'usr'\n");
        printf("whoison         --------- check server who is online\n");
        printf("logout/bye/exit --------- logout and exit client\n");
        printf("Ctrl+a click yourself refresh friend list, other will be chosen as a direct target\n");
        printf("Ctrl+x will quit the program\n");
        printf("getIElist get internet list\n");
        return 0;
    }else if(strcmp(input, "logout")==0 ||
            strcmp(input, "exit")==0 ||
            strcmp(input, "bye")==0)
    {
        *name = SERV_NAME;
        cli->pktsnd = packet_new(cli->name, SERV_NAME);
        cmd = CMD_LOGOUT;
        msg = (char *)g_cmd[cmd];
    }  else if (strncmp("to ", input, 3)==0){
        input += 3;
        if (line_parse(input, ':', &to, &msg)==0){
            cli->pktsnd = packet_new(cli->name, to);
        }
    } else if (*name && strncmp(*name,SERV_NAME,sizeof(SERV_NAME) )) {
        cli->pktsnd = packet_new(cli->name, *name);
        msg = input;
    }

    if (msg && cli->pktsnd){
        cli->pktsnd->msg[cli->pktsnd->nmsg] = strdup(msg);
        cli->pktsnd->nmsg++;
        ret = client_flush(cli);
        if (cmd==CMD_LOGOUT){
            exit(0);
        }
    }

    return ret;
}

ServerCmd server_parse_cmd(char *msg)
{
    ServerCmd cmd;
    for (cmd=0; cmd<CMD_LAST; cmd++){
        if(strcmp(msg, g_cmd[cmd])==0){
            return cmd;
        }
    }
    return CMD_LAST;
}

int packet_add_msg(ChatPacket *pkt, const char *msg)
{
    if (pkt->nmsg>=MAXMSG || msg==NULL){
        return -1;
    }
    pkt->msg[pkt->nmsg] = strdup(msg);
    pkt->nmsg++;

    return 0;
}

int client_parse_head(ChatClient *cli, char *str)
{
    char *data = "";
    HeadType type = packet_head_parse(str);

    if (HEAD_LAST == type)
        return 0;

    /* we can not be sure of the order */
    if (type!=HEAD_START && cli->pktget == NULL) {
        return 0;
    }

    if (type!= HEAD_START && type!=HEAD_END){
        data = packet_head_getdata(type, str);
    }

    switch(type){
        case HEAD_START:
        {
            if(cli->pktget){
                printf(" HEAD_START comes late\n");
                return -1;
            }
            cli->pktstat = PKT_READ;
            cli->pktget = packet_new(NULL, NULL);
            break;
        }
        case HEAD_TO:
            {
                if (cli->pktget->to)
                    return 0;
                cli->pktget->to = strdup(data);
                break;
            }
        case HEAD_FROM:
            {
                if (cli->pktget->from)
                    return 0;
                cli->pktget->from = strdup(data);
                break;
            }
        case HEAD_MSG:
            packet_add_msg(cli->pktget, data);
            break;
        case HEAD_TIME:
            {
                if (cli->pktget->time)
                    return 0;
                cli->pktget->time = strdup(data);
                break;
            }
        case HEAD_FDBK:
            {
                if (cli->pktget->fdbk)
                    return 0;
                cli->pktget->fdbk = strdup(data);
                break;
            }
        case HEAD_END:
        {
            if (cli->pktget->fdbk==NULL && cli->pktget->nmsg == 0) {
                return -1;
            }
            if (cli->pktget->to==NULL)
                return -1;
            if (cli->pktget->from==NULL){
                cli->pktget->from = strdup(cli->name);
            }
            if (cli->pktget->time==NULL){
                cli->pktget->time = gettime();
            }
            cli->pktstat = PKT_RDFULL;
            break;
        }
        default:
            break;
    }

    return 0;
}
